
# -*- coding: utf-8 -*-
import socket, cPickle, threading, time, base64, subprocess
#Фиксирование времени запуска первого задания и завершения последенего
locked = threading.Lock()
flag_of_close = 0
useless_task = []
uncomplete_flag = 0

PATH_INTERPRETER = 'C:\\Python27\\python '
PATH_TASKS = ''
#PATH_TASKS = 'A:\\'

MY_PORT = 8000 #Порт текущего компьютера
MY_IP = '127.0.0.1' #IP адрес текущего компьютера
MY_TASKS = [1, 2, 3, 4, 5] #Массив из номеров заданий, которые должен выполнить текущий ПК
MY_PARENT_IP = '1.1.1.1' #IP адрес родителя

socket.setdefaulttimeout(5)

class Worker(threading.Thread): #Класс выполняющий через POpen задание, которое лежит в одноименном файле.
    number = 0
    def __init__(self, number_of_task):
        self.number = number_of_task-1
        threading.Thread.__init__(self)
    def run(self):
        global current_status, current_result, current_zadan, spisok_zadan, complete, uncomplete_flag
        print(u'Начало выполнения работы ' + str(spisok_zadan[self.number][0]) + '\n')
        #locked.acquire()
        current_status = 'processing' #Выставляем статус, означающий выполнение задачи.
        current_zadan = spisok_zadan[self.number]
        current_result = 0
        #locked.release()
        tick_before = time.time()
        p = subprocess.Popen(PATH_INTERPRETER + PATH_TASKS + str(current_zadan[0]), shell=False, stdout=subprocess.PIPE) #Запускаем файл на выполнение.
        so, err = p.communicate()
        tick_after = time.time()
        work_time = tick_after - tick_before
        print ('work_time = ' + str(work_time))
        #locked.acquire()
        if (len(useless_task) != 0) :
            for i in useless_task:
                if (i == current_zadan[0]):
                    #locked.release()
                    print(u"Задание " + str(spisok_zadan[self.number][0]) + u" уже выполнено"  + '\n')
                    uncomplete_flag = 1
                    break
                else:
                    continue
        if not uncomplete_flag:
            current_status = 'done' #Выставляем статус завершения выполнения задачи.
            current_result = so
            spisok_zadan[self.number][1] = current_status
            spisok_zadan[self.number][2] = current_result
            spisok_zadan[self.number][3] = work_time
            complete = 1
            task[0] = spisok_zadan[self.number][0]
            task[1] = current_status
            task[2] = current_result
            task[3] = work_time
            print(u"Работа "+ str(spisok_zadan[self.number][0]) +u" выполнена" + '\n')
            uncomplete_flag = 0
        locked.acquire()

class Server(threading.Thread): #Клас сервера. Запускается в новом потоке и ждет подключений. На каждое подключение создаёт новый поток и обрабатывает его.
    my_addr = ''
    global flag_of_close 
    class MyThread(threading.Thread): #Класс потока, который запускает сервер, когда происходит новое соединение.
            my_addr = ''
            def __init__(self, MY_IP):
                    self.my_addr = MY_IP
                    threading.Thread.__init__(self)
            def run(self):
                    global flag_of_close, recv_str, sock_resp, load_data, ipTable, data, spisok_zadan, data_str, MY_PARENT_IP, damp_data, MY_TASKS, addr_resp
                    while 1:
                            try:
                                    recv_str = sock_resp.recv(8096)
                                    #print ("Client: "+str(recv_str) + '\n')
                                    #locked.acquire()
                                    if (recv_str[:10] == 'Transfert:'): #Если пришло ключевое слово 'Transfert:' начинаем приём файлов.
                                        print('Start tasks receiving with ' + str(addr_resp) + '\n')
                                        col = int(recv_str[10:])  #Сохраняем количество передаваемых файлов
                                        for i in range(col):
                                            recv_str = sock_resp.recv(1024)                                        
                                            if (recv_str[:5] == 'send:'): #Если пришло ключевое слово 'send:' пишем его в файл на диск.
                                                file = open(PATH_TASKS + str(recv_str[5:]), "wb")
                                                print('Start receiving '+str(recv_str[5:]) + '\n')
                                                while (1):
                                                    recv_str = sock_resp.recv(4096)
                                                    if (recv_str == 'stop!') or (recv_str == ''): #Если пришло ключевое слово 'stop!' или пустая строка, заканчиваем прием.
                                                        print('Receiving '+str(recv_str[5:])+' is done' + '\n')
                                                        recv_str = ''
                                                        data_str = ''
                                                        file.close()
                                                        time.sleep(2)
                                                        break
                                                    data_str = cPickle.loads(recv_str) #Распаковываем пришедшие данные.
                                                    recv_str = ''
                                                    file.write(data_str) #Пишем их в файл.
                                                    data_str = ''
                                        print('Stop tasks receiving with ' + str(addr_resp) + '\n')
                                        sock_resp.close()
                                        #locked.release()
                                        break
             
                                    if (recv_str[:5] == 'task:'): #Если пришло ключевое слово 'task:' то обновляем полученные результаты.
                                        load_data = cPickle.loads(recv_str[5:])
                                        for i in range(len(spisok_zadan)):
                                                if (spisok_zadan[i][0] == load_data[0]) and (spisok_zadan[i][1] != 'done'):
                                                    spisok_zadan[i][1] = load_data[1]
                                                    spisok_zadan[i][2] = load_data[2]
                                                    spisok_zadan[i][3] = load_data[3]
                                                    useless_task.append(load_data[0])
                                        #locked.release()
                                        sock_resp.close()
                                        print("useless_task = " + str(useless_task))
                                        break
                                     
                                    if (recv_str[:5] == 'data:'): #Если пришло ключевое слово 'data:' обновляем все данные.
                                        load_data = cPickle.loads(recv_str[5:])
                                        ipTable = load_data['ipTable']
                                        spisok_zadan = load_data['spisok_zadan']
                                        print(u"Данные обновлены" + '\n')
                                        recv_str = ''
                                        #locked.release()
                                        sock_resp.close()
                                        break
                                    
                                    if (recv_str[:5] == 'verb:'): # Если пришло ключевое слово 'verb:' проверяем завербованы ли мы.
                                        print(u"Получили вербовочный запрос!" + '\n')
                                        if (MY_PARENT_IP != '0.0.0.0'): #Если установлен ip владельца.
                                            data_str = 'cancel'
                                            sock_resp.send(data_str)
                                            print(u"Отвечаем, что уже завербованы" + '\n') 
                                            recv_str = ''
                                            sock_resp.close()
                                            #locked.release()
                                            break
                                        else: #Если ip владельца не установлен, то отвечаем, что готовы получить задание.
                                            data_str = 'ready'
                                            MY_PARENT_IP = recv_str[5:]
                                            sock_resp.send(data_str)
                                            print(u"Передаем состояние готовности новому родителю" + '\n') 
                                            recv_str = sock_resp.recv(1024)
                                            print(u"Ждём задания" + '\n')
                                            MY_TASKS = cPickle.loads(recv_str)
                                            print(u"Обновили полученные данные" + '\n')
                                            print (u'Для выполнения получены задания = '+str(MY_TASKS) + '\n')
                                            recv_str = ''
                                            sock_resp.close()
                                            #locked.release()
                                            break
                                    
                                    if (recv_str=='exit'): #Если пришло ключевое слово 'exit' отключаемся от клиента.
                                            print("Client was disconnected" + '\n')
                                            #locked.release()
                                            sock_resp.close()
                                            break
                                    
                                    if (recv_str=='close'): #Если пришло ключевое слово 'close' завершаем работу.
                                            flag_of_close = 1
                                            #locked.release()
                                            print('Socket will close' + '\n')
                                            sock_resp.close()
                                            break
                                    #locked.release()
                            except socket.timeout:                                                              
                                    #print("Client timeout" + '\n')
                                    pass
                            except socket.error:
                                    print("Client is down" + '\n')
                                    sock_resp.close()
                                    break
    
    def __init__(self, MY_IP):
            self.my_addr = MY_IP
            threading.Thread.__init__(self)

    def run(self):
            global flag_of_close_exit, sock_resp, addr_resp
            srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            srv.settimeout(5)
            srv.bind((self.my_addr, MY_PORT)) #Стартуем сервер на указанном порту указанного ip адреса.
            srv.listen(13)
            while 1:
                    try:
                            sock_resp, addr_resp = srv.accept()
                            sock_resp.settimeout(5)
                    except socket.timeout:
                            #print('server timeout!!' + '\n')
                            if (flag_of_close == 1):
                                    break
                            continue
                    self.MyThread(self.my_addr).setDaemon(True) #Запускает дочерние потоки как демоны, чтобы они уничтожались вместе с сервером.
                    self.MyThread(self.my_addr).start()
                    
                    
#Вербовочная информация: "verb:192.168.1.1"
#Готовность выполнять задания: "ready"
#Обновленный список: "data:(...)"
#Задание потомку: "task:(1,2,3,4,5)"



sock_resp = '' #Сокет ответа
addr_resp = '' #Адрес ответа
load_data = '' #Лоад полученных данных
damp_data = '' #Дамп передаваемых данных
all_result = 1

flag_of_close_exit = 0 #Флаг выхода
flag = 0
recv_str = '' #Полученная строка
data_str = '' #Строка простых ответов на запросы

my_child_ip = '0.0.0.0' #IP адрес потомка, которому переданы задания
complete = 0 #Флаг выполнения задания

current_zadan = [] #Номер выполняемого в данный момент на этой машине задания
current_number_of_zadan = 0
current_result = 0 #Результат выполняемого задания
current_status = 'processing' #Статус выполняемого задания

#Список заданий с именем результатами выполнения и статусом
spisok_zadan = [['Zadanie1.py','uncomplete','0',0], ['Zadanie2.py','uncomplete','0',0], ['Zadanie3.py','uncomplete','0',0], ['Zadanie4.py','uncomplete','0',0], ['Zadanie5.py','uncomplete','0',0]]
#Список заданий, который будет передан другому агенту для исполнения
tasks = []
#Словарь IP адресов, где каждый IP адрес ключ. Элементами являются массивы принадлежащих адресу заданий и состояние машины
ipTable = {'127.0.0.1':[[],'up',8000], 'localhost':[[],'up',8009]}
#Словарь данных, передаваемых между агентами. Здесь передается таблица адресов, весь список заданий.
data = {"ipTable":ipTable, "spisok_zadan":spisok_zadan}
#Одно выполненное задание.
task = ['name','status','result',0]

Server(MY_IP).setDaemon(True) #Объявляем поток демоном (если закрывается вызвавший поток, то закрывается и вызванный поток).
Server(MY_IP).start() #Старт потока сервера.
print(u"Сервер запущен" + '\n')

'''ПЕРЕДАЧА ФАЙЛОВ ЗАДАНИЙ НА ВСЕ РАБОТАЮЩИЕ АГЕНТЫ'''
for addr in ipTable.keys(): #Перебираем все адреса.
    if ((addr == MY_IP) and (ipTable[addr][2] == MY_PORT)): continue #Не обрабатываем свой IP
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Соединяемся с каждым клиентом.
    print ipTable[addr][2]
    try:
        client.connect((addr, ipTable[addr][2]))
    except socket.error:
        ipTable[addr][1] = 'down'
        print("IP = " + str(addr) + " is down on connecting" + '\n')
        continue
    try:
        client.send('Transfert:' + str(len(spisok_zadan))) #Объявляем о начале передачи файлов.
        time.sleep(0.5)
        print('Start sending file to ' + str(addr) + '\n')
    except socket.timeout:    
        ipTable[addr][1] = 'down'    
        print("IP = " + str(addr) + " is down on sending" + '\n')    
        client.close()    
        client = None    
        file.close()    
        break    
    for number in spisok_zadan: #Перебираем все задания, прописанные в переменной spisok_zadan.
        file = open(number[0], "rb") #Открываем для чтения файл с соответствующим именем.
        data_str = 'send:'+str(number[0]) 
        try:
            client.send(data_str)
        except socket.timeout:
            ipTable[addr][1] = 'down'
            print("IP = "+str(addr)+" is down on sending" + '\n')
            client.close()
            client = None
            file.close()
            break
        time.sleep(0) #Ждем 
        while (1):
            data_str = file.read(512) #Читаем из файла по 512 байт.
            if (not data_str):  #Если файл пустой, то завершаем цикл передачи.
                break
            damp_data = cPickle.dumps(data_str) #Компануем данные.
            try:
                client.send(damp_data) #Отправляем данные.
            except socket.timeout:
                ipTable[addr][1] = 'down'
                print("IP = "+str(addr)+" is down on sending" + '\n')
                client.close()
                client = None
                break
        print('Sending ' +str(number[0])+ ' is done' + '\n')
        file.close() #Закрываем файл.
        time.sleep(0)
        data_str = 'stop!'
        try:
            client.send(data_str) #Отсылаем сообщение об окончании передачи файла.
        except socket.timeout:
            ipTable[addr][1] = 'down'
            print("IP = "+str(addr)+" is down on sending" + '\n')
            client.close()
            client = None
            break
        time.sleep(0)
    client.close()
    client = None

    time.sleep(0)
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Соединяемся с каждым клиентом.
    try:
        client.connect((addr, ipTable[addr][2]))
    except socket.error:
        ipTable[addr][1] = 'down'
        print("IP = " + str(addr) + " is down on connecting" + '\n')
        continue
    print("Sending the Data structure")
    data_str = 'data:'
    damp_data = damp_data = cPickle.dumps(data)
    try:
        client.send(data_str + damp_data)
    except socket.timeout:
        ipTable[addr][1] = 'down'
        print("IP = "+str(addr)+" is down on sending" + '\n')
        client.close()
        client = None
        break
    client.close()
    client = None

    
'''ОСНОВНОЙ ПОТОК ОБРАБОТКИ ЗАДАНИЙ'''
while True:
    time.sleep(3) #Задержка в 5 секунд перед обработкой каждого следущего задания.
    all_result = 1
    if len(spisok_zadan) == 0:
        all_result = 0
    for i in spisok_zadan:
        if (i[1] != 'done'):
            all_result = 0
            print(u'Решение для задания  ' + str(i[0]) + u' ещё не найдено')
    locked.acquire()
    if (flag_of_close==1):
        print(u"Сервер остановлен" + '\n')
        locked.release()
        break
        
    if len(MY_TASKS): #Если у меня есть хотя бы одно задание, то 
        print(u'Количество моих заданий = '+str(len(MY_TASKS)))
    
        print(u"Запускаем на выполнение следующее задание" + '\n') #ОДНО ЗАПУСКАЕМ, ПОЛОВИНУ ОТСЫЛАЕМ.
        current_number_of_zadan = MY_TASKS.pop()
        current_zadan = spisok_zadan[current_number_of_zadan-1]
        job = Worker(current_number_of_zadan)
        job.setName(current_zadan[0])
        job.start() #Запуск в классе Worker на исполнение одного задания
        buffer = 0
                
        if (len(MY_TASKS) > 1): #Если осталось больше одного задания для выполнения, то
            buffer = len(MY_TASKS) / 2 #вычисляем половину, чтобы отдать их другому агенту
        elif (len(MY_TASKS) == 1):
            buffer = 1

        #Выбираем первый свободный адрес, если он существует, то отправляем на него половину заданий. 
        for addr in ipTable.keys(): #Перебираем все адреса в списке адресов.
            if ((ipTable[addr][0] == []) and (ipTable[addr][1] == 'up')):
                if ((addr == MY_IP) and (ipTable[addr][2] == MY_PORT)): continue #Не обрабатываем свой IP
                data_str = 'verb:' + MY_IP
                print(u"Передаем по "+str(addr)+u" запрос на вербовку" + '\n')
                client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                try:
                    client.connect((addr, ipTable[addr][2]))
                except socket.error:
                    ipTable[addr][1] = 'down'
                    print("IP = "+str(addr)+" is down on connecting" + '\n')
                    continue
                
                try:
                    client.send(data_str)
                    print(u"Ждем ответ: завербован или нет." + '\n')
                    recv_str = client.recv(256)
                except socket.timeout:
                    ipTable[addr][1] = 'down'
                    print("IP = "+str(addr)+" is down on sending" + '\n')
                    client.close()
                    client = None
                    continue
                    
                if (recv_str == 'ready'):
                    for i in range(buffer):
                        tasks.append(MY_TASKS.pop())
                    ipTable[addr][0] = tasks
                    damp_data = cPickle.dumps(tasks)
                    
                    try:
                        client.send(damp_data)
                    except socket.timeout:
                        ipTable[addr][1] = 'down'
                        print("IP = "+str(addr)+" is down" + '\n')
                        client.close()
                        client = None
                        continue
                        
                    client.close()
                    client = None
                    time.sleep(1)
                    
                elif (recv_str == 'cancel'):
                    print(u"Уже завербован" + '\n')
                    client.close()
                    client = None
                    time.sleep(1)
                    continue
                break
            else:
                continue
      
    if (complete): #Если текущая задача выполнена
        data_str = 'task:'
        for addr in ipTable.keys():
            if ((addr == MY_IP) and (ipTable[addr][2] == MY_PORT)): continue #Не обрабатываем свой IP
            if (ipTable[addr][1] == 'up'):
                print(u"Рассылаем в " +str(addr)+ u" результаты выполнения задания" + '\n')
                client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                try:
                    client.connect((addr, ipTable[addr][2]))
                except socket.error:
                    ipTable[addr][1] = 'down'
                    print("IP = "+str(addr)+" is down on connecting" + '\n')
                    continue
                damp_data = cPickle.dumps(task)
                try:
                    client.sendall(data_str+damp_data)
                except socket.timeout:
                    ipTable[addr][1] = 'down'
                    print("IP = "+str(addr)+" is down on sending" + '\n')
                    client.close()
                    client = None
                    continue
                client.close()
                client = None
        complete = 0
        data_str = ''
    else:
        if ((not all_result) and (current_status != 'processing')): #Если не все задачи выполнены, то
            print("len = " + str(len(MY_TASKS)))
            if (len(MY_TASKS) == 0): 
                for i in spisok_zadan:
                    if i[1] != 'done':
                        MY_TASKS.append(spisok_zadan.index(i)+1)
                        print("My new tasks is = " + str(spisok_zadan.index(i)))
        if all_result:
            print(u'Все задания выполнены!\n')
            print(u'Задание 1 = '+str(spisok_zadan[0][2])+str(spisok_zadan[0][3])+' \n')
            print(u'Задание 2 = '+str(spisok_zadan[1][2])+str(spisok_zadan[1][3])+' \n')
            print(u'Задание 3 = '+str(spisok_zadan[2][2])+str(spisok_zadan[2][3])+' \n')
            print(u'Задание 4 = '+str(spisok_zadan[3][2])+str(spisok_zadan[3][3])+' \n')
            print(u'Задание 5 = '+str(spisok_zadan[4][2])+str(spisok_zadan[4][3])+' \n')
            flag_of_close = 1
    locked.release()    
